/*
 Copyright 2009 Abdulla Abdurakhmanov (abdulla@latestbit.com)
 Original sources are available at www.latestbit.com

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

 http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.

 BigInteger.hpp. Created on: 01.03.2010 15:45:42 
 */

#ifndef BIGINTEGER_HPP_
#define BIGINTEGER_HPP_

#include "cpputils/CppUtilsDefinitions.hpp"
#include "cpputils/SystemException.hpp"

namespace cpputils {
namespace exttypes {

class BigInteger {
public:
	// Initialization

	inline BigInteger() {
		mpz_init2(nValue, 128);
	}

	inline BigInteger(const BigInteger& src) {
		mpz_init_set(nValue, src.getValueInInternalFormat());
	}

	inline BigInteger(const mpz_t& src) {
		mpz_init_set(nValue, src);
	}

	inline BigInteger(mpz_srcptr src) {
		mpz_init_set(nValue, src);
	}

	inline BigInteger(unsigned long nLongValue) {
		mpz_init_set_ui(nValue, nLongValue );
	}

	inline BigInteger(long nLongValue) {
		mpz_init_set_si(nValue, nLongValue );
	}

	inline BigInteger(double nDoubleValue) {
		mpz_init_set_d(nValue, nDoubleValue );
	}

	inline BigInteger(const std::string& sValueInStrForm) {
		if(mpz_init_set_str(nValue, sValueInStrForm.c_str(), 0 ) < 0) {
			mpz_clear(nValue);
			throw SystemException("Incorrect integer format is specified: "+sValueInStrForm);
		}
	}

	inline ~BigInteger() {
		mpz_clear(nValue);
	}

	const mpz_t& getValueInInternalFormat() const {
		return nValue;
	}

	mpz_ptr refValueInInternalFormat() {
		return nValue;
	}
	// Assigments

	inline void operator = (const BigInteger& src) {
		mpz_set(nValue, src.getValueInInternalFormat());
	}

	inline void operator = (const mpz_t& nInternalFormat) {
		mpz_set(nValue, nInternalFormat);
	}

	inline void operator = (unsigned long nSrcValue) {
		mpz_set_ui(nValue, nSrcValue);
	}

	inline void operator = (long nSrcValue) {
		mpz_set_si(nValue, nSrcValue);
	}

	inline void operator = (double nSrcValue) {
		mpz_set_d(nValue, nSrcValue);
	}

	inline void operator = (const std::string& sStrForm) {
		if(mpz_set_str(nValue, sStrForm.c_str(), 0) < 0)
			throw SystemException("Incorrect integer format is specified: "+sStrForm);
	}

	// Conversions
	inline operator unsigned long() const {
		return mpz_get_ui(nValue);
	}

	inline operator signed long () const {
		return mpz_get_si(nValue);
	}

	inline operator double () const {
		return mpz_get_d(nValue);
	}

	inline operator std::string() const {
		return toString();
	}

	inline std::string toString(int nBase=10) const {
		char* pOutputStr = NULL;
		pOutputStr = mpz_get_str(pOutputStr, nBase, nValue);
		std::string sResult;
		if(pOutputStr)
			sResult = pOutputStr;
		free( pOutputStr );
		return sResult;
	}

	// Arithmetic

	// Unary +/-
	friend inline BigInteger operator+(const BigInteger& oSrc) {
		BigInteger oResult;
		mpz_abs(oResult.nValue, oSrc.getValueInInternalFormat());
		return oResult;
	}

	friend inline BigInteger operator-(const BigInteger& oSrc) {
		BigInteger oResult;
		mpz_neg(oResult.nValue, oSrc.getValueInInternalFormat());
		return oResult;
	}

	// Binary +, -, *, /

	friend inline BigInteger operator+(const BigInteger& oFirst, const BigInteger& oSecond) {
		BigInteger oResult;
		mpz_add(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond.getValueInInternalFormat());
		return oResult;
	}

	friend inline BigInteger operator+(const BigInteger& oFirst, unsigned long oSecond) {
		BigInteger oResult;
		mpz_add_ui(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond);
		return oResult;
	}

	inline void operator+=(const BigInteger& oSrc) {
		mpz_t nNewValue;
		mpz_init2(nNewValue, 128);
		mpz_add(nNewValue, nValue, oSrc.getValueInInternalFormat());
		mpz_set(nValue, nNewValue);
		mpz_clear(nNewValue);
	}

	inline void operator+=(const unsigned long oSrc) {
		mpz_t nNewValue;
		mpz_init2(nNewValue, 128);
		mpz_add_ui(nNewValue, nValue, oSrc);
		mpz_set(nValue, nNewValue);
		mpz_clear(nNewValue);
	}

	friend inline BigInteger operator-(const BigInteger& oFirst, const BigInteger& oSecond) {
		BigInteger oResult;
		mpz_sub(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond.getValueInInternalFormat());
		return oResult;
	}

	friend inline BigInteger operator-(const BigInteger& oFirst, unsigned long oSecond) {
		BigInteger oResult;
		mpz_sub_ui(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond);
		return oResult;
	}

	inline void operator-=(const BigInteger& oSrc) {
		mpz_t nNewValue;
		mpz_init2(nNewValue, 128);
		mpz_sub(nNewValue, nValue, oSrc.getValueInInternalFormat());
		mpz_set(nValue, nNewValue);
		mpz_clear(nNewValue);
	}

	inline void operator-=(const unsigned long oSrc) {
		mpz_t nNewValue;
		mpz_init2(nNewValue, 128);
		mpz_sub_ui(nNewValue, nValue, oSrc);
		mpz_set(nValue, nNewValue);
		mpz_clear(nNewValue);
	}

	friend inline BigInteger operator*(const BigInteger& oFirst, const BigInteger& oSecond) {
		BigInteger oResult;
		mpz_mul(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond.getValueInInternalFormat());
		return oResult;
	}

	friend inline BigInteger operator*(const BigInteger& oFirst, unsigned long oSecond) {
		BigInteger oResult;
		mpz_mul_ui(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond);
		return oResult;
	}

	inline void operator*=(const BigInteger& oSrc) {
		mpz_t nNewValue;
		mpz_init2(nNewValue, 128);
		mpz_mul(nNewValue, nValue, oSrc.getValueInInternalFormat());
		mpz_set(nValue, nNewValue);
		mpz_clear(nNewValue);
	}

	inline void operator*=(const unsigned long oSrc) {
		mpz_t nNewValue;
		mpz_init2(nNewValue, 128);
		mpz_mul_ui(nNewValue, nValue, oSrc);
		mpz_set(nValue, nNewValue);
		mpz_clear(nNewValue);
	}

	friend inline BigInteger operator/(const BigInteger& oFirst, const BigInteger& oSecond) {
		BigInteger oResult;
		mpz_tdiv_q(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond.getValueInInternalFormat());
		return oResult;
	}

	friend inline BigInteger operator/(const BigInteger& oFirst, unsigned long oSecond) {
		BigInteger oResult;
		mpz_tdiv_q_ui(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond);
		return oResult;
	}

	inline void operator/=(const BigInteger& oSrc) {
		mpz_t nNewValue;
		mpz_init2(nNewValue, 128);
		mpz_tdiv_q(nNewValue, nValue, oSrc.getValueInInternalFormat());
		mpz_set(nValue, nNewValue);
		mpz_clear(nNewValue);
	}

	inline void operator/=(const unsigned long oSrc) {
		mpz_t nNewValue;
		mpz_init2(nNewValue, 128);
		mpz_tdiv_q_ui(nNewValue, nValue, oSrc);
		mpz_set(nValue, nNewValue);
		mpz_clear(nNewValue);
	}

	friend inline BigInteger operator%(const BigInteger& oFirst, const BigInteger& oSecond) {
		BigInteger oResult;
		mpz_mod(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond.getValueInInternalFormat());
		return oResult;
	}

	friend inline BigInteger operator%(const BigInteger& oFirst, unsigned long oSecond) {
		BigInteger oResult;
		mpz_mod_ui(oResult.nValue, oFirst.getValueInInternalFormat(), oSecond);
		return oResult;
	}

	inline void operator%=(const BigInteger& oSrc) {
		mpz_t nNewValue;
		mpz_init2(nNewValue, 128);
		mpz_mod(nNewValue, nValue, oSrc.getValueInInternalFormat());
		mpz_set(nValue, nNewValue);
		mpz_clear(nNewValue);
	}

	inline void operator%=(const unsigned long oSrc) {
		mpz_t nNewValue;
		mpz_init2(nNewValue, 128);
		mpz_mod_ui(nNewValue, nValue, oSrc);
		mpz_set(nValue, nNewValue);
		mpz_clear(nNewValue);
	}

	// Comparations
	inline bool operator > (const BigInteger &src) const {
		return mpz_cmp(nValue, src.getValueInInternalFormat()) > 0;
	}
	inline bool operator > (const unsigned long src) const {
		return mpz_cmp_ui(nValue, src) > 0;
	}
	inline bool operator > (const long src) const {
		return mpz_cmp_si(nValue, src) > 0;
	}
	inline bool operator > (const double src) const {
		return mpz_cmp_d(nValue, src) > 0;
	}

	inline bool operator < (const BigInteger &src) const {
		return mpz_cmp(nValue, src.getValueInInternalFormat()) < 0;
	}
	inline bool operator < (const unsigned long src) const {
		return mpz_cmp_ui(nValue, src) < 0;
	}
	inline bool operator < (const long src) const {
		return mpz_cmp_si(nValue, src) < 0;
	}
	inline bool operator < (const double src) const {
		return mpz_cmp_d(nValue, src) < 0;
	}

	inline bool operator >= (const BigInteger &src) const {
		return mpz_cmp(nValue, src.getValueInInternalFormat()) >= 0;
	}
	inline bool operator >= (const unsigned long src) const {
		return mpz_cmp_ui(nValue, src) >= 0;
	}
	inline bool operator >= (const long src) const {
		return mpz_cmp_si(nValue, src) >= 0;
	}
	inline bool operator >= (const double src) const {
		return mpz_cmp_d(nValue, src) >= 0;
	}

	inline bool operator <= (const BigInteger &src) const {
		return mpz_cmp(nValue, src.getValueInInternalFormat()) <= 0;
	}
	inline bool operator <= (const unsigned long src) const {
		return mpz_cmp_ui(nValue, src) <= 0;
	}
	inline bool operator <= (const long src) const {
		return mpz_cmp_si(nValue, src) <= 0;
	}
	inline bool operator <= (const double src) const {
		return mpz_cmp_d(nValue, src) <= 0;
	}

	inline bool operator == (const BigInteger &src) const {
		return mpz_cmp(nValue, src.getValueInInternalFormat()) == 0;
	}
	inline bool operator == (const unsigned long src) const {
		return mpz_cmp_ui(nValue, src) == 0;
	}
	inline bool operator == (const long src) const {
		return mpz_cmp_si(nValue, src) == 0;
	}
	inline bool operator == (const double src) const {
		return mpz_cmp_d(nValue, src) == 0;
	}

	inline bool operator != (const BigInteger &src) const {
		return mpz_cmp(nValue, src.getValueInInternalFormat()) != 0;
	}
	inline bool operator != (const unsigned long src) const {
		return mpz_cmp_ui(nValue, src) != 0;
	}
	inline bool operator != (const long src) const {
		return mpz_cmp_si(nValue, src) != 0;
	}
	inline bool operator != (const double src) const {
		return mpz_cmp_d(nValue, src) != 0;
	}

	// Output
	friend inline std::istream& operator>>(std::istream &oStream, BigInteger &src) {
		std::string sTempStringValue;
		oStream >> sTempStringValue;
		src = sTempStringValue;
		return oStream;
	}

	friend inline std::ostream& operator<<(std::ostream &oStream, const BigInteger &src) {
		oStream << src.toString();
		return oStream;
	}

	// Predefined consts
public:
	CPPU_DLLPRFX const static BigInteger ZERO;
	CPPU_DLLPRFX const static BigInteger ONE;
	CPPU_DLLPRFX const static BigInteger TWO;
	CPPU_DLLPRFX const static BigInteger FOUR;
	CPPU_DLLPRFX const static BigInteger EIGHT;
	CPPU_DLLPRFX const static BigInteger TEN;
	CPPU_DLLPRFX const static BigInteger N16;
	CPPU_DLLPRFX const static BigInteger N32;
	CPPU_DLLPRFX const static BigInteger N64;
	CPPU_DLLPRFX const static BigInteger N128;
	CPPU_DLLPRFX const static BigInteger HUNDRED;
	CPPU_DLLPRFX const static BigInteger THOUSAND;
	CPPU_DLLPRFX const static BigInteger N1024;


protected:
	mpz_t nValue;
};

}
}

#endif /* BIGINTEGER_HPP_ */
